package de.kuttenmann.jflake;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

// Traced 84.553.389 rays in 242.284 ms. Zwei Kerne Seq
// Traced 84.553.389 rays in 164.856 ms. Zwei Kerne Parallel
//Traced 11850678 rays in 74803 ms.
public class Tracer {

    private Node scene;
    private int width;
    private int height;
    private int depth;

    /**
     * Creates a new tracer with a given scene, a certain resolution of the
     * target image and a certain recursion depth for the ray tracing algorithm.
     * 
     * @param scene
     *            The scene to be rendered
     * @param width
     *            The width of the target image
     * @param height
     *            The height of the target image
     * @param depth
     *            The recursion depth for the ray tracing algorithm
     */
    public Tracer(Node scene, int width, int height, int depth) {
	this.scene = scene;
	this.width = width;
	this.height = height;
	this.depth = depth;
    }

    private static int rays;

    /**
     * TODO Make width, height, depth and level configurable by program
     * arguments
     * 
     * @param args
     *            The program arguments
     */
    public static void main(String[] args) {
	int width = 1280;
	int height = 720;
	int depth = 4;
	int level = 4;

	Node scene = SceneBuilder.create(level, Vector3D.ZERO, new Vector3D(
		0.25, 1.0, -0.5).normalize(), 1.0);
	Tracer tracer = new Tracer(scene, width, height, depth);

	rays = 0;
	long startTime = System.currentTimeMillis();
	try {
	    tracer.trace();
	} catch (IOException ioe) {
	    ioe.printStackTrace();
	}
	long endTime = System.currentTimeMillis();

	System.out.format("Traced %d rays in %d ms.\n", rays,
		(endTime - startTime));
    }

    /**
     * This method generates an image as specified by this <code>Tracer</code>
     * instance. The image is automatically written to <code>./jflake.ppm</code>
     * . The PPM format is an open standard and can be viewed with several image
     * viewers, e.g. IrfanView. For more information see Wikipedia or Google.
     * 
     * @throws IOException
     *             If an error occurs while writing the image
     */
    public void trace() throws IOException {
	// Pool aus 2*Kernenanzahl Treads
	ExecutorService prozessors = Executors.newFixedThreadPool(Runtime
		.getRuntime().availableProcessors() * 2);
	MyExecutorService executor = new MyExecutorService(prozessors);

	// Array mit den Ergebnissen
	ArrayList<Future<Vector3D[]>> futureArray = new ArrayList<Future<Vector3D[]>>();

	double viewX;
	double viewY = height / 2.0 - 0.5;
	final double viewZ = Math.max(width, height); // war auf FINAL geaendert

	Task task = new Task(this, scene, depth);
	/* each line */
	for (int y = 0; y < height; y++) {
	    viewX = -width / 2.0 + 0.5;
	    /* each pixel on scan line */

	    for (int x = 0; x < width; x++) {
		task.addViewPoint(new Vector3D(viewX, viewY, viewZ));
		viewX += 1.0;
	    }
	    // jeder Durchlauf der inneren Schleife als Task definieren
	    futureArray.add(executor.submit(task));
	    task = new Task(this, scene, depth);
	    /* next line */
	    viewY -= 1.0;
	}

	// Ausgabe in eine Datei
	FileOutputStream fout = new FileOutputStream("jflake_reference.ppm");
	/* PPM header */
	fout.write("P6\n".getBytes());
	fout.write(String.format("%d %d\n", width, height).getBytes());
	fout.write("255\n".getBytes());

	// Iterator ueber Ergebnisse
	Iterator<Future<Vector3D[]>> it = futureArray.iterator();
	while (it.hasNext()) {
	    Future<Vector3D[]> colorFuture = it.next();
	    Vector3D[] colors;
	    try {
		colors = colorFuture.get();

		for (int i = 0; i < colors.length; i++) {
		    fout.write(clamp(colors[i].getX()));
		    fout.write(clamp(colors[i].getY()));
		    fout.write(clamp(colors[i].getZ()));
		}
	    } catch (InterruptedException e) {
		e.printStackTrace();
	    } catch (ExecutionException e) {
		e.printStackTrace();
	    }
	}

	fout.close();
	executor.shutdownNow();
    }

    /**
     * This method clamps a (color component) value to a value between 0 and
     * 255.
     * 
     * @param value
     *            The value to be clamped
     * @return The clamped value between 0 and 255
     */
    public static int clamp(double value) {
	return (int) Math.max(0.0, Math.min(value, 255.0));
    }
    public synchronized void addRays(int rayCount) {
	rays += rayCount;
}
}
